<script lang="ts">
  let recipes = new Map<string, [string, number][]>()
  let results = new Map<string, Map<string, number>>()

  function calcItem(item: string, recipes: Map<string, [string, number][]>, results: Map<string, Map<string, number>>) {
    let result = results.get(item)

    if (result) {
      return result
    }

    const recipe = recipes.get(item)!;

    if (recipe.length > 0) {
      result = new Map()

      for (const [subItem, subCount] of recipe) {
        let subResult = calcItem(subItem, recipes, results)

        for (const [addItem, addCount] of subResult) {
          result.set(addItem, (result.get(addItem) || 0) + addCount * subCount)
        }
      }
    } else {
      result = new Map([[item, 1]])
    }

    results.set(item, result)
    return result
  }

  $: {
    const newResults = new Map<string, Map<string, number>>()

    for (const [item] of recipes) {
      calcItem(item, recipes, newResults)
    }

    results = newResults
  }

  let newItem = ''
  let newInputs = new Map<string, number>()
  let newInputName = ''
  let newInputCount = 1

  function selectItem(item: string) {
    newInputName = item
  }

  function editItem(item: string) {
    newItem = item
  }

  function deleteItem(item: string) {
    recipes.delete(item)
    for (const [_, recipe] of recipes) { // delete recipe of dependents
      for (const [subItem] of recipe) {
        if (subItem == item) {
          recipe.length = 0
        }
      }
    }
    recipes = recipes
    newInputs = new Map()
  }

  function addItem() {
    if (newItem) {
      if (recipes.has(newItem)) { // change recipe
        for (const [input] of newInputs) {
          if (results.get(input)!.has(newItem)) { // circular dependency
            return
          }
        }
      }

      recipes = recipes.set(newItem, [...newInputs])
      newItem = ''
      newInputs = new Map()
    }
  }

  function addInput() {
    if (newInputCount > 0 && recipes.has(newInputName) && !newInputs.has(newInputName)) {
      newInputs = newInputs.set(newInputName, newInputCount)
      newInputName = ''
      newInputCount = 1
    }
  }

  function deleteInput(item: string) {
    newInputs.delete(item)
    newInputs = newInputs
  }

  function loadFromLocalStorage() {
    recipes = new Map(JSON.parse(localStorage.getItem('recipes') || '[]'))
  }

  function saveToLocalStorage() {
    localStorage.setItem('recipes', JSON.stringify([...recipes]))
  }
</script>

<div>
  {#each recipes as [output, inputs]}
    <div>
      <button on:click={() => selectItem(output)}>{output}</button>
      <span>
        {#each inputs || [] as [item, count], i}
          {i > 0 ? ' +' : ''} {item} * {count}
        {/each}
      </span>
      <button on:click={() => editItem(output)}>Edit</button>
      <button on:click={() => deleteItem(output)}>Delete</button>
    </div>
    <div>
      {#each results.get(output) || new Map() as [item, count], i}
        {i > 0 ? ' +' : ''} {item} * {count}
      {/each}
    </div>
  {/each}
</div>

<div>
  <div>
    <input bind:value={newItem}>
    <button on:click={addItem}>Add Item / Change Recipe</button>
  </div>
  <div>
    {#each newInputs as [item, count]}
      <div>
        {item} * {count}
        <button on:click={() => deleteInput(item)}>Delete</button>
      </div>
    {/each}
    <div>
      <input bind:value={newInputName}>
      *
      <input bind:value={newInputCount} type="number">
      <button on:click={addInput}>Add Input</button>
    </div>
  </div>
  <div>
    <button on:click={loadFromLocalStorage}>Load From Local Storage</button>
    <button on:click={saveToLocalStorage}>Save To Local Storage</button>
  </div>
</div>
